﻿// <copyright file="_DIRECTORY_STRUCT_PRIMARY.cs" company="Sinclair Community College">
// Copyright 2010 Sinclair Community College
// </copyright>
// <originalAuthor>Patrick S. Seymour (patrick.seymour@gmail.com)</originalAuthor>

namespace SinclairCC.AppV
{
    using System;
    using System.IO;

    /// <summary>
    /// This structure contains the metadata of a file or a directory.
    /// </summary>
    public class _DIRECTORY_STRUCT_PRIMARY
    {
        /// <summary>
        /// Indicates the type of the _DIRECTORY_STRUCT_PRIMARY structure.
        /// </summary>
        private StructureTypeId structureType;

        /// <summary>
        /// Total size of this structure.
        /// </summary>
        private uint dirDataLen;

        /// <summary>
        /// The offset of this structure entry within the parent directory entry table (the
        /// children directory structures that reside within a parent).
        /// </summary>
        private uint dirDataOffset;

        /// <summary>
        /// The index of the corresponding container that describes this structure.
        /// </summary>
        private uint containerIndex;

        /// <summary>
        /// Security entity of the owner of this file or directory.
        /// </summary>
        private uint ownerId;

        /// <summary>
        /// Unused.
        /// </summary>
        private byte[] reserved;

        /// <summary>
        /// The time when the file/directory was created.
        /// </summary>
        private DateTime creationTime;

        /// <summary>
        /// The time when the file/directory was last modified.
        /// </summary>
        private DateTime modificationTime;

        /// <summary>
        /// The time when the file/directory was last accessed.
        /// </summary>
        private DateTime accessTime;

        /// <summary>
        /// Current allocation length of the file.
        /// </summary>
        private ulong allocationLength;

        /// <summary>
        /// The last byte written for the given file or directory.
        /// </summary>
        /// <remarks>
        /// Information beyond the Valid Data Length will read as zero values.
        /// For directory type, this value is always zero (0).
        /// </remarks>
        private ulong validDataLength;

        /// <summary>
        /// The actual size of the file's valid data length, as it is stored on App-V Streaming Server.
        /// </summary>
        private ulong softDataLength;

        /// <summary>
        /// The security identifier of the primary group for the file/directory.
        /// </summary>
        /// <remarks>
        /// This is principally for POSIX support, where the group ID is mandatory.
        /// </remarks>
        private uint primaryGroupId;

        /// <summary>
        /// These are the standard Windows file attributes.
        /// </summary>
        private uint fileAttributes;

        /// <summary>
        /// Bit mask of persistent flags that states additional information about the data structures
        /// pointed to by this container.
        /// </summary>
        private SoftricityFileTypeFlags softricityFileTypeFlags;

        /// <summary>
        /// The length of the short file name (in bytes), not including the double byte terminating
        /// NULL character.
        /// </summary>
        private ushort shortNameLength;

        /// <summary>
        /// The length of the long file name (in bytes), not including the double byte terminating
        /// NULL character.
        /// </summary>
        private ushort fileNameLength;

        /// <summary>
        /// Short file name in WCHAR. This is an array of characters with fixed size 13 (26 bytes).
        /// </summary>
        private string shortName;

        /// <summary>
        /// Long file name in WCHAR.
        /// </summary>
        private string fileName;

        public _DIRECTORY_STRUCT_PRIMARY(System.IO.BinaryReader fileReader)
        {
            this.ReadContainer(fileReader);
        }
        
        /// <summary>
        /// Reads the actual data for this container from the file.
        /// </summary>
        protected void ReadContainer(System.IO.BinaryReader fileReader)
        {
            uint rawUInt;
            ushort rawUShort;

            rawUInt = 0;
            if (fileReader.BaseStream.Position <= (fileReader.BaseStream.Length - sizeof(uint)))
            {
                rawUInt = fileReader.ReadUInt32();
            }
            else
            {
                fileReader.BaseStream.Seek(0, System.IO.SeekOrigin.End);
            }

            // TODO: Is this appropriate? Is it true that if the structure type ID is zero, the container is empty?
            if (rawUInt == 0) return;

            if (Enum.IsDefined(typeof(StructureTypeId), rawUInt))
            {
                this.structureType = (StructureTypeId)rawUInt;
            }

            this.dirDataLen = fileReader.ReadUInt32();

            byte[] thisStructBytes = fileReader.ReadBytes((int)this.dirDataLen - (2 * sizeof(uint)));
            
            MemoryStream byteStream = new MemoryStream(thisStructBytes, false);
            System.IO.BinaryReader bufferReader = new System.IO.BinaryReader(byteStream);

            this.dirDataOffset = bufferReader.ReadUInt32();
            this.containerIndex = bufferReader.ReadUInt32();
            this.ownerId = bufferReader.ReadUInt32();
            this.reserved = bufferReader.ReadBytes(4);

            long fileTimeLong = bufferReader.ReadInt64();
            try
            {
                this.creationTime = DateTime.FromFileTimeUtc(fileTimeLong);
            }
            catch (ArgumentOutOfRangeException) { }

            fileTimeLong = bufferReader.ReadInt64();
            try
            {
                this.modificationTime = DateTime.FromFileTimeUtc(fileTimeLong);
            }
            catch (ArgumentOutOfRangeException) { }

            fileTimeLong = bufferReader.ReadInt64();
            try
            {
                this.accessTime = DateTime.FromFileTimeUtc(fileTimeLong);
            }
            catch (ArgumentOutOfRangeException) { }

            this.allocationLength = bufferReader.ReadUInt64();
            this.validDataLength = bufferReader.ReadUInt64();
            this.softDataLength = bufferReader.ReadUInt64();
            this.primaryGroupId = bufferReader.ReadUInt32();
            this.fileAttributes = bufferReader.ReadUInt32();

            rawUShort = bufferReader.ReadUInt16();
            this.softricityFileTypeFlags = (SoftricityFileTypeFlags)rawUShort;

            this.shortNameLength = bufferReader.ReadUInt16();
            this.fileNameLength = bufferReader.ReadUInt16();

            byte[] stringBytes;
            System.Text.UnicodeEncoding encoding = new System.Text.UnicodeEncoding();
            stringBytes = bufferReader.ReadBytes(26);
            if (this.shortNameLength <= stringBytes.Length)
            {
                this.shortName = encoding.GetString(stringBytes, 0, (int)this.shortNameLength);
            }

            stringBytes = bufferReader.ReadBytes(this.fileNameLength + 2);
            if (this.fileNameLength <= stringBytes.Length)
            {
                this.fileName = encoding.GetString(stringBytes, 0, (int)this.fileNameLength);
            }

            bufferReader.Close();
            byteStream.Close();
            byteStream.Dispose();
        }

        public StructureTypeId StructureType
        {
            get { return this.structureType; }
        }
        
        public uint DirDataLen
        {
            get { return this.dirDataLen; }
        }

        public uint DirDataOffset
        {
            get { return this.dirDataOffset; }
        }

        public uint ContainerIndex
        {
            get { return this.containerIndex; }
        }

        public uint OwnerId
        {
            get { return this.ownerId; }
        }

        public DateTime CreationTime
        {
            get { return this.creationTime; }
        }

        public DateTime ModificationTime
        {
            get { return this.modificationTime; }
        }

        public DateTime AccessTime
        {
            get { return this.accessTime; }
        }

        public ulong AllocationLength
        {
            get { return this.allocationLength; }
        }

        public ulong ValidDataLength
        {
            get { return this.validDataLength; }
        }

        public ulong FileSizeOnServer
        {
            get { return this.softDataLength; }
        }

        public uint PrimaryGroupId
        {
            get { return this.primaryGroupId; }
        }

        public uint Attributes
        {
            get { return this.fileAttributes; }
        }

        public SoftricityFileTypeFlags SoftricityFlags
        {
            get { return this.softricityFileTypeFlags; }
        }

        public string ShortName
        {
            get { return this.shortName; }
        }

        public string LongName
        {
            get { return this.fileName; }
        }
    }
}
